package ss.pku.jsa.type.inference;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.SortedMap;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Vector;

import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TextElement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;

import ss.pku.jsa.type.constraint.solver.QError;


@SuppressWarnings({ "rawtypes","unchecked" })
public class DeclarationVisitor extends ASTVisitor {
	protected CompilationUnit compilationUnit = null;
	protected String fileName = null;
	Analysis analysis = null;
	static final String QTYPE_PROPERTY = "QType";
	private SortedMap declarations = new TreeMap();

	private MethodQType currentMethod = null;
	private Map currentMethodLocals = null;
	private Stack currentMethodStack = new Stack();
	private Stack currentMethodLocalsStack = new Stack();
	private boolean original;
	private boolean logJavadoc = false;

	public static final Integer METHOD = new Integer(0);
	public static final Integer FIELD = new Integer(1);

	public DeclarationVisitor(CompilationUnit compilationUnit, String fileName,
			Analysis analysis, boolean original) {
		this.compilationUnit = compilationUnit;
		this.fileName = fileName;
		this.analysis = analysis;
		this.original = original;
	}

	public void endVisit(CompilationUnit node) {
		Profile.logStart("*DeclarationVisitor: apply javadoc");
		Iterator comments = node.getCommentList().iterator();
		while (comments.hasNext()) {
			Comment comment = (Comment) comments.next();

			if ((comment instanceof Javadoc)) {
				if (this.logJavadoc)
					JqualPlugin.printlnLog("javadocLog", "It's Javadoc.");
				ASTNode t = (ASTNode) this.declarations.get(new Integer(comment
						.getStartPosition()));

				if (t == null) {
					SortedMap tail = this.declarations.tailMap(new Integer(
							comment.getStartPosition()));
					try {
						t = (ASTNode) this.declarations.get(tail.firstKey());
					} catch (NoSuchElementException localNoSuchElementException) {
						t = null;
					}
				}

				if (t != null) {
					Location l = Location.getLocation(t, this.compilationUnit,
							this.fileName);
					if (this.logJavadoc) {
						JqualPlugin.printlnLog("javadocLog", "Connected to: "
								+ t + " at " + l);
					}
					if ((t instanceof TypeDeclaration)) {
						parseJavadoc((Javadoc) comment, (TypeDeclaration) t, l);
					} else if ((t instanceof MethodDeclaration)) {
						parseJavadoc((Javadoc) comment, (MethodDeclaration) t,
								l);
					} else if ((t instanceof CastExpression)) {
						parseJavadoc((Javadoc) comment, (CastExpression) t, l);
					} else if ((t instanceof VariableDeclarationStatement)) {
						VariableDeclarationStatement s = (VariableDeclarationStatement) t;
						for (Iterator i = s.fragments().iterator(); i.hasNext();) {
							VariableDeclaration d = (VariableDeclaration) i
									.next();
							parseJavadoc((Javadoc) comment, d, l);
						}
					} else if ((t instanceof SingleVariableDeclaration)) {
						VariableDeclaration d = (SingleVariableDeclaration) t;
						parseJavadoc((Javadoc) comment, d, l);
					} else if ((t instanceof FieldDeclaration)) {
						FieldDeclaration s = (FieldDeclaration) t;
						for (Iterator i = s.fragments().iterator(); i.hasNext();) {
							VariableDeclaration d = (VariableDeclaration) i
									.next();
							parseJavadoc((Javadoc) comment, d, l);
						}
					}

				}
			}
		}

		Profile.logEnd();
	}

	public boolean visit(SimpleName node) {
		Profile.logStart("Decl Visitor: visit(SimpleName node)");
		visitAnnotationTarget(node);
		IBinding b = node.resolveBinding();

		if ((b != null) && ((b instanceof ITypeBinding))) {
			ITypeBinding t = (ITypeBinding) b;
			this.analysis.addTypeFields(t);
		}
		Profile.logEnd();
		return true;
	}

	public boolean visit(CastExpression node) {
		visitAnnotationTarget(node);
		return true;
	}

	public void visitAnnotationTarget(ASTNode node) {
		ASTNode target = node;
		while ((target != null) && (!takesQualifier(target))) {
			target = target.getParent();
		}

		if (target != null) {
			if (this.logJavadoc)
				JqualPlugin.printlnLog("javadocLog", "Adding " + target
						+ " to targets");
			this.declarations.put(new Integer(node.getStartPosition()), target);
		}
	}

	private boolean takesQualifier(ASTNode node) {
		if ((!(node instanceof TypeDeclaration))
				&& (!(node instanceof MethodDeclaration))) {
			if ((!(node instanceof VariableDeclarationStatement))
					&& (!(node instanceof SingleVariableDeclaration))
					&& (!(node instanceof FieldDeclaration))
					&& (!(node instanceof Initializer))
					&& (!(node instanceof CastExpression)))
				return false;
		}
		return true;
	}

	public boolean visit(Initializer node) {
		this.currentMethodLocals = new HashMap();
		return true;
	}

	public void endVisit(Initializer node) {
		getLocation(node);

		this.currentMethodLocals = null;
	}

	public boolean visit(MethodDeclaration node) {
		Location l = getLocation(node);
		IMethodBinding m = node.resolveBinding();
		if (m == null) {
			this.analysis.logMissingClass(node.toString(), l);
			return true;
		}
		MethodKey key = MethodKey.getMethodKey(m);
		if (key.toString().contains("getText"))
			;
		MethodQType qt = (MethodQType) this.analysis.lookupQType(key);
		if (qt == null) {
			qt = QType.getFreshQType(m, l, this.original);
			this.analysis.addQType(key, qt);
		} else {
			qt.setOriginal(this.original);
		}

		this.analysis.addMethodDeclaration(key, qt);
		this.analysis.methodsDeclared.add(key);
		if (Modifier.isNative(m.getModifiers())) {
			if (JqualPlugin.inferOpaque) {
				FFIResults.JNIMethodName j = new FFIResults.JNIMethodName(m);
				JqualPlugin.analysis.nativeMethods.put(j, qt);

				j = new FFIResults.OverloadedJNIMethodName(m);
				JqualPlugin.analysis.nativeMethods.put(j, qt);
			}

		}

		if (this.currentMethod != null) {
			this.currentMethodStack.push(this.currentMethod);
			this.currentMethodLocalsStack.push(this.currentMethodLocals);
		}
		this.currentMethod = qt;
		this.currentMethodLocals = new HashMap();

		int p = 0;
		List paramQTypes = qt.getParameterQTypes();
		for (Iterator params = node.parameters().iterator(); params.hasNext(); p++) {
			SingleVariableDeclaration param = (SingleVariableDeclaration) params
					.next();
			RefQType paramQt = (RefQType) paramQTypes.get(p);
			this.analysis.addQType(param.resolveBinding().getKey(), paramQt);
			this.currentMethodLocals.put(param.getName().toString(), paramQt);
		}

		if (JqualPlugin.inferInterned) {
			if ((m.getName().equals("equals"))
					&& (m.getParameterTypes().length == 1)
					&& (!m.getDeclaringClass().equals(
							node.getAST().resolveWellKnownType(
									"java.lang.Object")))) {
				ClassQType cqt = this.analysis.getClassQType(key
						.getDeclaringType());
				Qualifier q = Qualifier.findQualifierConstant("noninterned");
				if (q == null) {
					JqualPlugin.out.println("noninterned at " + l);
					JqualPlugin.out.flush();
				}
				CQual.makeEdge(Constraint.Kind.LT, q, cqt.getQual(),
						this.analysis.gateInternedOnly, l, 0, QError.INTERNED);
			}
		}
		return true;
	}

//	private List<Annotation> getAnnotations(List modifiers) {
//		List ret = new Vector();
//		for (Iterator localIterator = modifiers.iterator(); localIterator
//				.hasNext();) {
//			Object mod = localIterator.next();
//			if ((mod instanceof Annotation)) {
//				ret.add((Annotation) mod);
//			}
//		}
//		return ret;
//	}

	private ClassQType getClassQType(ITypeBinding t) {
		if (t == null)
			return null;
		return this.analysis.getClassQType(t);
	}

	public void endVisit(MethodDeclaration node) {
		getLocation(node);

		node.setProperty("LOCALS", this.currentMethodLocals);
		if (!this.currentMethodStack.empty()) {
			this.currentMethod = ((MethodQType) this.currentMethodStack.pop());
			this.currentMethodLocals = ((Map) (Map) this.currentMethodLocalsStack
					.pop());
		} else {
			this.currentMethod = null;
			this.currentMethodLocals = null;
		}
	}

	public void endVisit(SingleVariableDeclaration node) {
		Location l = getLocation(node);
		IVariableBinding v = node.resolveBinding();
		if (v == null) {
			this.analysis.logMissingClass(node.toString(), l);
			return;
		}

		String key = v.getKey();

		RefQType qt = null;
		if ((isLocal(v))
				&& (this.currentMethod != null)
				&& (v.getVariableId() < this.currentMethod.getParameterQTypes()
						.size())) {
			qt = (RefQType) this.currentMethod.getParameterQTypes().get(
					v.getVariableId());

			if (qt == null) {
				throw new IllegalArgumentException("QType is null. Method: "
						+ this.currentMethod.getName() + " var: " + v.getName());
			}

		} else {
			qt = (RefQType) this.analysis.lookupQType(key);
			if (qt == null) {
				String name = this.analysis.createName(v);
				int d = node.getExtraDimensions();
				ITypeBinding t = v.getType();
				if (t == null) {
					this.analysis.logMissingClass(node.toString(), l);
					return;
				}
				qt = QType.getFreshRefQType(t, d, l, name, this.original,
						JqualPlugin.isStatic(v));
				this.analysis.addQType(key, qt);
			} else {
				qt.setOriginal(this.original);
			}
		}
		this.analysis.addVariableDeclaration(key, qt);
	}

	private boolean isLocal(IVariableBinding v) {
		return !v.isField();
	}

	public boolean visit(TypeDeclaration node) {
		Location l = getLocation(node);
		ITypeBinding t = node.resolveBinding();
		if (t == null) {
			throw new NullPointerException("binding null for " + node + " at "
					+ l);
		}
		TypeKey key = TypeKey.getTypeKey(t);
		ClassQType cqt = getClassQType(t);
		cqt.l = l;
		cqt.setOriginal(this.original);
		this.analysis.addClassDeclaration(key, cqt);
		this.analysis.typesDeclared.add(key);

		if (JqualPlugin.inferInterned) {
			ITypeBinding st = t.getSuperclass();
			if (st != null) {
				ClassQType scqt = getClassQType(st);
				CQual.makeEdge(Constraint.Kind.LT, scqt.q, cqt.q,
						this.analysis.gateInternedOnly, l, 0,
						QError.INHERITED_NONINTERNED);
			}
		}

		return true;
	}

	public void endVisit(TypeDeclaration node) {
		Location l = getLocation(node);
		ITypeBinding t = node.resolveBinding();

		IMethodBinding[] methods = t.getDeclaredMethods();

		for (int i = 0; i < methods.length; i++) {
			IMethodBinding m = methods[i];
			MethodKey key = MethodKey.getMethodKey(m);
			if (m.isConstructor()) {
				if (this.analysis.lookupQType(key) != null)
					break;
				this.analysis.addQType(key, QType.getFreshQType(m, l, false));
				this.analysis.methodsDeclared.add(key);

				break;
			}

		}

		Profile.logStart("createSupertypeConstraints");
		createSupertypeConstraints(node.resolveBinding(), l);
		Profile.logEnd();
	}

	public void endVisit(AnonymousClassDeclaration node) {
		Location l = getLocation(node);
		ITypeBinding t = node.resolveBinding();

		if (t == null) {
			this.analysis.logMissingClass(node.toString(), l);
			return;
		}

		TypeKey key = TypeKey.getTypeKey(t);

		ClassQType qt = this.analysis.lookupClassQType(key);
		if (qt == null) {
			String name = key.toString();
			Profile.logMemStart("endVisit(AnonymousClassDeclaration)/new ClassQType");
			qt = new ClassQType(t, l, name.toUpperCase(), this.original);
			Profile.logMemEnd();
			this.analysis.addClassQType(key, qt);
		}

		this.analysis.addClassDeclaration(key, qt);
		this.analysis.typesDeclared.add(key);

		IMethodBinding[] methods = t.getDeclaredMethods();

		for (int i = 0; i < methods.length; i++) {
			IMethodBinding m = methods[i];
			MethodKey mkey = MethodKey.getMethodKey(m);
			if (m.isConstructor()) {
				if (this.analysis.lookupQType(mkey) != null)
					break;
				this.analysis.createName(m);
				this.analysis.addQType(mkey, QType.getFreshQType(m, l, false));

				break;
			}

		}

		Profile.logStart("createSupertypeConstraints");
		createSupertypeConstraints(node.resolveBinding(), l);
		Profile.logEnd();
	}

	public void endVisit(VariableDeclarationFragment node) {
		Location l = getLocation(node);
		IVariableBinding v = node.resolveBinding();
		if (v == null) {
			this.analysis.logMissingClass(node.toString(), l);
			return;
		}

		String key = v.getKey();
		ITypeBinding t = v.getType();

		TypeClosureUtils.printCU(t);

		int d = node.getExtraDimensions();

		if (t == null) {
			this.analysis.logMissingClass(node.toString(), l);
			return;
		}
		if (t.getDimensions() > d)
			d = t.getDimensions();

		RefQType qt = null;
		if (v.isField()) {
			this.analysis.fieldsDeclared.add(FieldKey.getFieldKey(v));
		} else {
			qt = (RefQType) this.analysis.lookupQType(key);
			String name = this.analysis.createName(v);
			if (qt == null) {
				qt = QType.getFreshRefQType(t, d, l, name, this.original,
						JqualPlugin.isStatic(v));
				this.analysis.addQType(key, qt);
			}

			this.analysis.addVariableDeclaration(key, qt);

			if (this.currentMethodLocals != null) {
				this.currentMethodLocals.put(v.getName(), qt);
			}
		}
	}

	public boolean visit(Name node) {
		IBinding b = node.resolveBinding();
		Location l = getLocation(node);
		if (b == null) {
			this.analysis.logMissingClass(node.toString(), l);
		} else if (b.getKind() == 2) {
			ITypeBinding t = (ITypeBinding) b;
			getClassQType(t);
		}
		return true;
	}

	public static boolean isBooleanOperator(InfixExpression.Operator o) {
		return (o.equals(InfixExpression.Operator.CONDITIONAL_AND))
				|| (o.equals(InfixExpression.Operator.CONDITIONAL_OR))
				|| (o.equals(InfixExpression.Operator.EQUALS))
				|| (o.equals(InfixExpression.Operator.GREATER))
				|| (o.equals(InfixExpression.Operator.LESS))
				|| (o.equals(InfixExpression.Operator.GREATER_EQUALS))
				|| (o.equals(InfixExpression.Operator.LESS_EQUALS))
				|| (o.equals(InfixExpression.Operator.NOT_EQUALS));
	}

	public static boolean isAssignmentOperator(String op) {
		return Assignment.Operator.toOperator(op) != null;
	}

	private void constrainToConstant(Qualifier q, Qualifier c, QualGate qgate,
			Location loc, int polarity, QError error) {
		int sign = c.getSign();

		switch (sign) {
		case -1:
			CQual.makeEdge(Constraint.Kind.LT, q, c, qgate, loc, polarity,
					error);
			return;
		case 1:
			CQual.makeEdge(Constraint.Kind.LT, c, q, qgate, loc, polarity,
					error);
			return;
		case 0:
			CQual.makeEdge(Constraint.Kind.EQ, c, q, qgate, loc, polarity,
					error);
			return;
		}
		throw new IllegalArgumentException("Unexpected result: " + sign
				+ " for " + c);
	}

	private void parseJavadoc(Javadoc doc, VariableDeclaration decl, Location l) {
		IVariableBinding b = decl.resolveBinding();

		if (b == null) {
			this.analysis.logMissingClass(decl.getName().toString(), l);
			return;
		}

		if (doc == null) {
			return;
		}
		if (this.logJavadoc)
			JqualPlugin.printlnLog("javadocLog",
					"In parseJavadoc for " + b.getName() + ": " + doc);
		String key = b.getKey();

		RefQType qt = null;
		FieldKey fieldKey = null;

		if (b.isField()) {
			ITypeBinding t = b.getDeclaringClass();
			Profile.logMemStart("parseJavadoc/new FieldKey");
			fieldKey = FieldKey.getFieldKey(b);
			Profile.logMemEnd();
			ClassQType cqt = this.analysis.getClassQType(t);
			qt = cqt.lookupFieldQType(b);
			if (qt == null) {
				int d = b.getType().getDimensions();
				qt = QType.getFreshRefQType(b.getType(), d, l, b.getName(),
						this.original, JqualPlugin.isStatic(b));

				if ((JqualPlugin.inferOpaque) && (JqualPlugin.ffiInferPublic)
						&& ((b.getModifiers() & 0x1) != 0)) {
					CQual.makeEdge(Constraint.Kind.LT,
							JqualPlugin.analysis.qual_public, qt.deref()
									.getQual(), QualGate.EMPTY, l, 0,
							QError.FFI_PUBLIC_FIELD);
				}

				cqt.addFieldQType(b, qt);
			}
		} else {
			qt = (RefQType) this.analysis.lookupQType(key);
			if (qt == null) {
				throw new RuntimeException(
						"Why are we parsing javadoc before creating QType: "
								+ key + "\n at " + l + "\n" + doc);
			}
		}

		List tags = doc.tags();
		for (Iterator i = tags.iterator(); i.hasNext();) {
			TagElement t = (TagElement) i.next();
			String name = t.getTagName();
			if (name == null)
				continue;
			name = name.trim();
			if (this.logJavadoc) {
				JqualPlugin.printlnLog("javadocLog",
						"In parseJavadoc for " + b.getName()
								+ " looking at tag: " + name);
			}
			List args = t.fragments();
			if ((JqualPlugin.obeyFSAnnotations)
					&& (name.equals("@FieldSensitive"))) {
				if (fieldKey == null)
					throw new IllegalArgumentException(
							"@FieldSensitive applied to local var at " + l);
				this.analysis.fieldSensitiveFields.add(fieldKey);
				if (this.logJavadoc)
					JqualPlugin.printlnLog("javadocLog", "added " + fieldKey
							+ " to FS, made " + qt + " non-global");
			} else if ((args.size() > 0)
					&& ((args.get(0) instanceof TextElement))) {
				String[] param = ((TextElement) args.get(0)).getText().trim()
						.split(" ");
				Qualifier c = null;
				if (this.logJavadoc)
					JqualPlugin.printlnLog("javadocLog", "tag is " + name);
				if ((name.equals("@qual")) || (name.equals("@refqual"))
						|| (name.equals("@valqual"))) {
					String qualName = param[0].trim();
					c = Qualifier.findQualifierConstant(qualName);
					if (c == null) {
						JqualPlugin.out.println(param[0].trim() + " at " + l);
						JqualPlugin.out.flush();
					}
					if ((!name.equals("@valqual"))
							&& (((name.equals("@qual")) && (c.getLevel() == 1)) || (name
									.equals("@refqual")))) {
						Qualifier qual = qt.getQual();
						if (this.logJavadoc)
							JqualPlugin.printlnLog("javadocLog", "setting " + c
									+ " == " + qual);
						constrainToConstant(qual, c, QualGate.EMPTY, l, 0,
								QError.SPEC);
						if ((!JqualPlugin.inferReadonly) || (!this.original)
								|| (!qualName.equals("readonly")))
							continue;
						this.analysis.readonlyAnnotations.add(qual);
					} else if ((name.equals("@qual"))
							|| (name.equals("@valqual"))) {
						Qualifier qual = qt.deref().getQual();
						if (this.logJavadoc)
							JqualPlugin.printlnLog("javadocLog", "setting " + c
									+ " == " + qual);
						constrainToConstant(qual, c, QualGate.EMPTY, l, 0,
								QError.SPEC);
						if ((!JqualPlugin.inferReadonly) || (!this.original)
								|| (!qualName.equals("readonly")))
							continue;
						this.analysis.readonlyAnnotations.add(qual);
					}
				} else if (name.equals("@qMutable")) {
					param[0].trim();
					Profile.logMemStart("parseJavadoc/new FieldKey (2)");
					FieldKey fk = FieldKey.getFieldKey(b);
					Profile.logMemEnd();
					this.analysis.setMutable(fk);
				}
			}
		}
	}

	private void parseJavadoc(Javadoc doc, MethodDeclaration m, Location l) {
		IMethodBinding b = m.resolveBinding();
		if (b == null) {
			this.analysis.logMissingClass(m.getName().toString(), l);
			return;
		}

		if (doc == null) {
			return;
		}
		if (this.logJavadoc) {
			JqualPlugin.printlnLog("javadocLog",
					"In parseJavadoc for " + b.getName() + ": " + doc);
		}
		MethodKey key = MethodKey.getMethodKey(b);
		QType qt = this.analysis.lookupQType(key);
		if (qt == null) {
			throw new RuntimeException(
					"Why are we parsing javadoc before creating QType: " + key
							+ "\n at " + l + "\n" + doc);
		}
		List tags = doc.tags();
		for (Iterator i = tags.iterator(); i.hasNext();) {
			TagElement t = (TagElement) i.next();
			String name = t.getTagName();
			if (name == null)
				continue;
			if (this.logJavadoc) {
				JqualPlugin.printlnLog("javadocLog",
						"In parseJavadoc for " + b.getName()
								+ " looking at tag: " + name);
			}
			List args = t.fragments();
			if ((args.size() > 0) && ((args.get(0) instanceof TextElement))) {
				String[] param = ((TextElement) args.get(0)).getText().trim()
						.split(" ");
				Qualifier c = null;
				if (this.logJavadoc) {
					JqualPlugin.printlnLog("javadocLog", "tag is " + name);
				}
				if ((name.equals("@qreturn")) || (name.equals("@qthis"))
						|| (name.equals("@qthisref"))) {
					String qualName = param[0].trim();
					c = Qualifier.findQualifierConstant(qualName);
					if (c == null) {
						JqualPlugin.out.println(param[0].trim() + " at " + l);
						JqualPlugin.out.flush();
					}
					if (name.equals("@qreturn")) {
						if (((MethodQType) qt).getReturnQType() == null) {
							throw new IllegalArgumentException(
									"Return QType qualifier specified for void method: "
											+ qt + " at " + l);
						}
						Qualifier qual = ((MethodQType) qt).getReturnQType()
								.getQual();
						constrainToConstant(qual, c, QualGate.EMPTY, l, 0,
								QError.SPEC);

						if ((!JqualPlugin.inferReadonly) || (!this.original)
								|| (!qualName.equals("readonly")))
							continue;
						this.analysis.readonlyAnnotations.add(qual);
					} else if (name.equals("@qthis")) {
						Qualifier qual = ((MethodQType) qt).getThisQType()
								.getQual();
						constrainToConstant(qual, c, QualGate.EMPTY, l, 0,
								QError.SPEC);
						if ((!JqualPlugin.inferReadonly) || (!this.original)
								|| (!qualName.equals("readonly")))
							continue;
						this.analysis.readonlyAnnotations.add(qual);
					}
				} else if ((name.equals("@qparam")) || (name.equals("@qlocal"))
						|| (name.equals("@qparamref"))
						|| (name.equals("@qlocalref"))) {
					if (param.length < 2) {
						throw new RuntimeException("Bad javadoc: " + name
								+ " used with only 1 arg: " + b.getName());
					}
					HashMap methodLocals = (HashMap) m.getProperty("LOCALS");
					qt = (RefQType) methodLocals.get(param[0].trim());
					if (qt == null) {
						throw new RuntimeException("Bad javadoc: " + name
								+ " used on unknown name: " + param[0]);
					}
					String qualName = param[1].trim();
					c = Qualifier.findQualifierConstant(qualName);
					if (c == null) {
						JqualPlugin.out.println(param[0].trim() + " at " + l);
						JqualPlugin.out.flush();
					}
					if ((name.equals("@qparam")) || (name.equals("@qlocal"))) {
						Qualifier qual = ((RefQType) qt).deref().getQual();
						constrainToConstant(qual, c, QualGate.EMPTY, l, 0,
								QError.SPEC);

						if ((!JqualPlugin.inferReadonly) || (!this.original)
								|| (!qualName.equals("readonly")))
							continue;
						this.analysis.readonlyAnnotations.add(qual);
					} else {
						constrainToConstant(qt.getQual(), c, QualGate.EMPTY, l,
								0, QError.SPEC);
						if ((!JqualPlugin.inferReadonly) || (!this.original)
								|| (!qualName.equals("readonly")))
							continue;
						this.analysis.readonlyAnnotations.add(qt.getQual());
					}
				}
			}
		}
	}

	private void parseJavadoc(Javadoc doc, TypeDeclaration d, Location l) {
		ITypeBinding b = d.resolveBinding();

		if (b == null) {
			this.analysis.logMissingClass(d.getName().toString(), l);
			return;
		}

		if (doc == null) {
			return;
		}
		String key = b.getKey();

		List tags = doc.tags();
		for (Iterator i = tags.iterator(); i.hasNext();) {
			TagElement t = (TagElement) i.next();
			String name = t.getTagName();
			if (name == null)
				continue;
			ClassQType qt = this.analysis.lookupClassQType(TypeKey
					.getTypeKey(b));
			if (qt == null) {
				throw new RuntimeException(
						"Why are we parsing javadoc before creating QType: "
								+ key + "\n at " + l + "\n" + doc);
			}
			List args = t.fragments();
			if ((args.size() <= 0)
					|| (!(args.get(0) instanceof TextElement))
					|| ((!name.equals("@qual")) && (!name.equals("@valqual")) && (!name
							.equals("@refqual"))))
				continue;
			String[] param = ((TextElement) args.get(0)).getText().trim()
					.split(" ");
			Qualifier c = Qualifier.findQualifierConstant(param[0].trim());
			if (c == null) {
				JqualPlugin.out.println(param[0].trim() + " at " + l);
				JqualPlugin.out.flush();
				return;
			}
			if ((!name.equals("@valqual"))
					&& (((name.equals("@qual")) && (c.getLevel() == 1)) || (name
							.equals("@refqual"))))
				constrainToConstant(qt.getRefQual(), c, QualGate.EMPTY, l, 0,
						QError.SPEC);
			else if ((name.equals("@qual")) || (name.equals("@valqual")))
				constrainToConstant(qt.getQual(), c, QualGate.EMPTY, l, 0,
						QError.SPEC);
		}
	}

	private void parseJavadoc(Javadoc doc, CastExpression node, Location l) {
		if (doc == null) {
			return;
		}
		List tags = doc.tags();
		Qualifier cq = Qualifier.makeQualifierVariable(
				"cast_annot" + l.getLine(), l, this.original);
		for (Iterator i = tags.iterator(); i.hasNext();) {
			TagElement t = (TagElement) i.next();
			String name = t.getTagName();
			if (name == null) {
				continue;
			}
			List args = t.fragments();
			if ((args.size() > 0) && ((args.get(0) instanceof TextElement))) {
				String[] param = ((TextElement) args.get(0)).getText().trim()
						.split(" ");
				Qualifier c = null;
				if (name.equals("@qual")) {
					c = Qualifier.findQualifierConstant(param[0].trim());
					if (c == null) {
						JqualPlugin.out.println(param[0].trim() + " at " + l);
						JqualPlugin.out.flush();
					}
					constrainToConstant(cq, c, QualGate.EMPTY, l, 0,
							QError.SPEC);
				}
			}
		}

		this.analysis.setQualifier(l, cq);
		if (this.logJavadoc)
			JqualPlugin.printlnLog("javadocLog", "SETTING " + cq + " At " + l);
	}

	public static List getSig(IMethodBinding m) {
		List ret = new Vector();
		ret.add(METHOD);
		ret.add(m.getName());
		ret.add(m.getReturnType().getKey());
		ITypeBinding[] params = m.getParameterTypes();
		for (int i = 0; i < params.length; i++) {
			ret.add(params[i].getKey());
		}
		return ret;
	}

	public static List getSig(IVariableBinding f) {
		List ret = new Vector();
		ret.add(FIELD);
		ret.add(f.getName());
		ret.add(f.getType().getKey());
		return ret;
	}

	private Map createSupertypeConstraints(ITypeBinding type, Location l) {
		String key = type.getKey();
		this.analysis.getClassQType(type);

		if (this.analysis.processedTypes.containsKey(key)) {
			return (Map) this.analysis.processedTypes.get(key);
		}
		Map memberMap = addMemberQTypes(type, l);

		int length = type.getInterfaces().length;
		if (type.getSuperclass() != null) {
			length++;
		}
		ITypeBinding[] supertypes = new ITypeBinding[length];
		System.arraycopy(type.getInterfaces(), 0, supertypes, 0,
				type.getInterfaces().length);
		if (type.getSuperclass() != null) {
			supertypes[(length - 1)] = type.getSuperclass();
		}
		if (key.contains("StringAndInt"))
			;
		for (int i = 0; i < supertypes.length; i++) {
			ITypeBinding st = supertypes[i];

			Map stMemberMap = createSupertypeConstraints(st, l);

			Iterator stMembers = stMemberMap.keySet().iterator();

			while (stMembers.hasNext()) {
				Object stMemberSig = stMembers.next();
				QType stQt = (QType) stMemberMap.get(stMemberSig);

				if ((stQt instanceof MethodQType)) {
					if (memberMap.containsKey(stMemberSig)) {
						QType qt = (QType) memberMap.get(stMemberSig);
						CQual.makeMethodConstraint((MethodQType) qt,
								(MethodQType) stQt, QualGate.EMPTY, l, 0,
								QError.INHERIT);
					} else {
						memberMap.put(stMemberSig, stQt);
					}
				}
			}
		}

		this.analysis.processedTypes.put(key, memberMap);
		return memberMap;
	}

	private Map addMemberQTypes(ITypeBinding type, Location l) {
		Map memberMap = new HashMap();

		type.getDeclaredFields();
		IMethodBinding[] m = type.getDeclaredMethods();

		for (int i = 0; i < m.length; i++) {
			IMethodBinding mb = m[i];
			MethodKey key = MethodKey.getMethodKey(m[i]);
			QType mq = this.analysis.lookupQType(key);
			if (mq == null) {
				try {
					mq = QType.getFreshQType(mb, l, false);
					this.analysis.addQType(key, mq);
				} catch (IllegalArgumentException e) {
					System.err.println("Error creating member method qtype");
					System.err.println(mb.getKey());
					System.err.println(mb.getDeclaringClass()
							.getQualifiedName());
					System.err.println("returns: "
							+ mb.getReturnType().getKey());
					System.err.println(mb.getReturnType().getKind());
					throw e;
				}
			}
			memberMap.put(getSig(m[i]), mq);
		}

		return memberMap;
	}

	private Location getLocation(ASTNode node) {
		return Location.getLocation(node, this.compilationUnit, this.fileName);
	}
}
